<!DOCTYPE html>
<html lang="en">
    <head>
        <meta charset="UTF-8" />
        <meta name="viewport" content="width=device-width, initial-scale=1.0" />
        <meta http-equiv="X-UA-Compatible" content="ie=edge" />
        <title>my-vue</title>
    </head>
    <body>
        <h1 id="app">
            这里是{{ name }}
            <div><span>3333</span></div>
            <div><span>{{title}}</span></div>
        </h1>
    </body>

    <script>
        let transfer = function(str) {
            str = str.replace("class", "className");
            for (var i = 0; i < str.length; i++) {
                if (str[i] == "-" && str[i + 1]) {
                    str = str.replace(
                        `-${str[i + 1]}`,
                        str[i + 1].toLocaleUpperCase()
                    );
                }
            }
            return str;
        };

        let res = transfer(
            '<div class="my-class" class="to-nens">测试数据</div>'
        );
        console.log(res); // 要求res 通过转换后得到 '<div className="myClass" class="toNens">测试数据</div>'

        // 实现数据变化拦截
        function defineReactive(data, key, val) {
            observe(val); // 递归遍历所有子属性
            var dep = new Dep();
            Object.defineProperty(data, key, {
                enumerable: true,
                configurable: true,
                get: function() {
                    if (Dep.target) {
                        // 判断是否需要添加订阅者
                        dep.addSub(Dep.target); // 在这里添加一个订阅者
                    }
                    return val;
                },
                set: function(newVal) {
                    if (val === newVal) {
                        return;
                    }
                    val = newVal;
                    console.log(
                        "属性" +
                            key +
                            "已经被监听了，现在值为：“" +
                            newVal.toString() +
                            "”"
                    );
                    dep.notify(); // 如果数据变化，通知所有订阅者
                }
            });
        }
        function observe(data) {
            if (!data || typeof data !== "object") {
                return;
            }
            Object.keys(data).forEach(function(key) {
                defineReactive(data, key, data[key]);
            });
        }

        function Dep() {
            this.subs = [];
        }
        Dep.target = null;
        Dep.prototype = {
            addSub: function(sub) {
                this.subs.push(sub);
            },
            notify: function() {
                this.subs.forEach(function(sub) {
                    sub.update();
                });
            }
        };

        function Watcher(vm, exp, cb) {
            this.cb = cb;
            this.vm = vm;
            this.exp = exp;
            this.value = this.get(); // 将自己添加到订阅器的操作
        }

        Watcher.prototype = {
            update: function() {
                this.run();
            },
            run: function() {
                var value = this.vm.data[this.exp];
                var oldVal = this.value;
                if (value !== oldVal) {
                    this.value = value;
                    this.cb.call(this.vm, value, oldVal);
                }
            },
            get: function() {
                Dep.target = this; // 缓存自己
                var value = this.vm.data[this.exp]; // 强制执行监听器里的get函数
                Dep.target = null; // 释放自己
                return value;
            }
        };

        function SelfVue(options) {
			let self = this;
			this.vm = this;
            this.data = options.data;
            // data[key] 代理到 data.data[key]
            Object.keys(this.data).forEach(function(key) {
                self.proxyKeys(key);
            });
            observe(this.data);
            new Compile(options.el,this.vm)
            return this;
        }
        SelfVue.prototype = {
            proxyKeys: function(key) {
                Object.defineProperty(this, key, {
                    get: function() {
                        return this.data[key];
                    },
                    set: function proxySetter(newVal) {
                        this.data[key] = newVal;
                    }
                });
            }
		};
		// 编译 ---------------------------------------
		let Compile = function(el,vm){
			this.vm = vm;
			this.el = document.querySelector(el)
			this.fragment = null
			this.init();
		}
		Compile.prototype = {
			init : function(){
				if(this.el){
					this.fragment = this.nodeToFragment(this.el)
					this.compileElement(this.fragment)
					this.el.appendChild(this.fragment)
				}else{
					console.log("DOM不存在")
				}
			},
			nodeToFragment(el) {
				var fragment = document.createDocumentFragment();
				var child = el.firstChild;
				while (child) {
					fragment.appendChild(child);
					child = el.firstChild;
				}
				return fragment;
			},
			compileElement (el) {
				let nodeArr = el.childNodes;
				let childNodes = Array.prototype.slice.call(nodeArr);
				childNodes.map(node => {
					var reg = /\{\{(.*)\}\}/;
					if (this.isTextNodes(node) && reg.test(node.nodeValue)) {
						// console.log(reg.exec(node.nodeValue)[1])
						let value = reg.exec(node.nodeValue)[1] || "";
						value = value.trim();
						this.compileText(node, value);
					}
					if (node.childNodes && node.childNodes.length) {
						this.compileElement(node);
					}
				});
			},
			compileText (node, exp) {
				let self = this
				var initText = this.vm[exp];
				this.updateText(node, initText);
				new Watcher(this.vm, exp, function(value) {
					self.updateText(node, value);
				});
			},
			updateText(node, value) {
				node.textContent = typeof value === "undefined" ? "" : value;
			},
			isTextNodes (node) {
				return ["#text", "SPAN", "LABEL"].indexOf(node.nodeName) >= 0;
			}
		}
        // 提取页面上所有的节点元素


		var selfVue = new SelfVue(
			{
				el : '#app',
				data : {
					title : 'this is ittle',
					name : 'init name'
				}
			});

        window.setTimeout(function() {
            selfVue.name = "new name";
            selfVue.title = "new title";
        }, 2000);
    </script>
</html>


<!-- https://www.cnblogs.com/libin-1/p/6893712.html -->
